/*-
 * Copyright (c) 2007-2008 SINA Corporation, All Rights Reserved.
 *  Authors:
 *      Zhu Yan <zhuyan@staff.sina.com.cn>
 *
 * Sample library mail parser Implementation.
 * Parse a mail and distill mail information, such like IP.
 */

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <parsemail/parsemail.h>


#include "log.h"
#include "lmtp.h"
#include "libmod.h"
#include "mfile.h"

int mod_init(char *mod_name);
#ifdef __cplusplus
extern "C" {
#endif

#define MAX_BUF_LEN        1023
#define MAX_T_LEN        127   // max token length
#define MAX_ID_LEN        31    // max identifier length
#define _TOKEN_LEN        31
#define K_IP_LEN        15

#define ISNUM(ch)        (ch>='0' && ch<='9')

#define isblock(c)        ((c) == '[' || (c) == ']' || (c) == '(' || (c) == ')')

// Enumeration of token types.
enum tok_types { 
    UNDEFTT = 0, 
    IDENTIFIER, 
    DELIMITER, 
    KEYWORD, 
    BLOCK, 
    TEMP
};

// Enumeration of internal representation of tokens.
enum token_ireps { 
    _UNDEFTOK = 0,
    _END,
    X_SINA_ORIGINATING_IP,
    RECEIVED, RECEIVED_FROM,
    RECEIVED_BY, 
    RECEIVED_WITH, 
    RECEIVED_FOR, 
    FROM, 
    TO,
    SUBJECT, 
    DATE, 
    MESSAGE_ID
};

struct _tokens {
    char    token[_TOKEN_LEN+1];
    int        tok;
};

typedef struct token_info {
    int    token_type;
    int    token_irep;
} token_info_t;


#define MAILPARSER_ERROR    mod_conf_priority

struct _tokens token_table[] = {
  {"x-sina-originating-ip:", X_SINA_ORIGINATING_IP},
  {"from:", FROM},
  {"to", TO},
  {"date:", DATE},
  {"", _END}
};


/* name:     is_ip_str
 * function: judge whether the string ip is an ip address
 * parameters:
 *     const char *ip: the source string which maybe an ip address
 * return type: static int
 *         !0: it's an ip
 *           0: it's not an ip

 */
static int 
is_ip_str(const char *ip)
{
    const char *p = NULL;

    if(!ip || strlen(ip) > K_IP_LEN)
        return (0);

    const char *p1 = NULL;
    const char *p2 = NULL;
    int seg = 0;

    p1 = ip;
    while((p2 = strstr(p1, "."))||(seg == 3))
    {
        if(seg == 3)//the last segment
            p2 = ip + strlen(ip);
        if((p2-p1)>3 || (p2-p1)<1)
            return (0);
        for(p=p1; p<p2; p++)
        {
            if(!ISNUM(*p))
                return (0);
        }
        p1 = p2+1;
        seg ++;
    }
    
    return (seg == 4);
}


static int
getnstr(char *buf, const char *str, size_t n)
{
    int r = 0;

	if (n != 0) {
        char *d = buf;
        const char *s = str;

    /* 
     * clean the space after the first charactor of '\r', '\n' or 0 in the string
     * put the string into buffer
     * and get the position that the string ends
     */
        do {
            *d = *s;
            if ( *d == '\r' || *d == '\n' || *d == 0) {
                /* NUL pad the remaining n-1 bytes */
                r = d - buf;
                while (--n != 0)
                    *d++ = 0;
                break;
            }
            d++;
            s++;
        } while (--n != 0);
    }

    /* return the position that the string ends.*/
    return (r);
}

static int 
look_up(char *s)
{
    int i;
    char *p;
  
    p = s;
    while (*p) {
        *p = tolower(*p); 
        p++; 
    }
  
    for (i=0; *token_table[i].token; i++) {
        if (!strncmp(token_table[i].token, s, _TOKEN_LEN))
            return token_table[i].tok;
    }
  
    return _UNDEFTOK; /* unknown command */
}

/* Get a token. */
static token_info_t
get_token(char **prog, char *token)
{
    int token_type;
    char *temp;
    int tok;
    token_info_t ti;


    token_type = _UNDEFTOK; tok = _UNDEFTOK;

    temp = token;
    *temp = '\0';

    /* Skip over white space. */
    while (isspace(**prog) && **prog)
        ++(*prog);

    /* Skip over newline. */
    if (**prog == '\r' || **prog == '\0') {
        tok = _END;
        ti.token_irep = _END;
        ti.token_type = DELIMITER;
        return ti;
    }

    /* Check for block delimiters. */
    if (strchr("[]()", **prog)) { 
        *temp = **prog;
        temp++;
        *temp = '\0';
        (*prog)++;
        ti.token_irep = tok;
        ti.token_type = BLOCK;
        return ti;
    }

    /* Read identifier or keyword. */
    if (isalpha(**prog) || isdigit(**prog)) {
        while(!isspace(**prog) && !isblock(**prog)) {
            if((temp - token) < MAX_ID_LEN) 
              *temp++ = **prog;
            (*prog)++;
        }
        token_type = TEMP;
    }
  
    *temp = '\0';
  
    /* Determine if token is a keyword or identifier. */
    if (token_type == TEMP) {
        tok = look_up(token); /* convert to internal form */
        if(tok) 
            token_type = KEYWORD; /* is a keyword */
        else 
            token_type = IDENTIFIER;
    }
    ti.token_irep = tok;
    ti.token_type = token_type;
    return ti;
}


static inline int 
ip2str(const uint32_t ip, char *ipstr, size_t len)
{
    struct in_addr in;
    in.s_addr = ip;
    if (inet_ntop(AF_INET, &in, ipstr, len) == NULL)
        return 1;
    return 0;
}

static inline int 
str2ip(const char *ipstr, uint32_t *ip)
{
    struct in_addr in;
    if (ip == NULL)
        return 1;
    if (inet_pton(AF_INET, ipstr, &in) != 1)
        return 1;
    *ip = in.s_addr;
    return 0;
}


/* name:     parse_ip
 * function: parse the ip from the mail
 * parameters:
 *     mail_info_t *mi: the mail information
 *     char *src:       original string that maybe include the ip
 * return type: static int
 *          0: get the ip or reach the end
 *           1: can not parse out the ip
 * called by:   parse in mailparser.c
 */
static int
parse_ip(MAIL_INFO *mip, char *str) 
{
    int        blocked = 0;
    token_info_t ti;
    char    *s = str;
    char    token[MAX_T_LEN+1];

    /* pass X_SINA_ORIGINATING_IP*/
    get_token(&s, token);
    for (;;) {
        ti = get_token(&s, token);
        if (ti.token_irep == _END)
            break;
        switch (ti.token_type) {
        case BLOCK:
            if (blocked)
                return (1);
            else
                blocked = 1;
            break;
        case IDENTIFIER:
            if (is_ip_str(token))
                strlcpy(mip->ip_str, token, MAX_IP_LEN+1);
            return (0);
        default:
            return (1);
        }
    }
    return (0);
}

/* name:     parse
 * function: parse the imformation from the mail
 * parameters:
 *     mail_info_t *mail_info: the buffer
 *     const char *src:        original string that needs to be parse
 * return type: static int
 *          2: 
 *         any: return the position that the word ends.
 * called by:   mail_handler in mailparser.c
 */
static int 
mail_parser(MAIL_INFO *mip, const char *src)
{
    token_info_t ti;
    char buf[MAX_BUF_LEN+1];
    char *b;
    char token[MAX_T_LEN+1];
    strlcpy(buf, src, MAX_BUF_LEN+1);
    b = buf;
    
    ti = get_token(&b, token);
    mip->time = time(&(mip->time));

    switch (ti.token_irep) {
    case X_SINA_ORIGINATING_IP:
        return parse_ip(mip, buf);
    default:
        return (2);
    }
}

/*
 * decode mail from buffer_head and insert mailinfo to db_queue
 * name:     mail_handler
 * function: parse the mail head
 * parameters:
 *     void *req: the request_t type
 *     void *res: ARGUSED
 * return type: int
 *           0: operate successful
 *          -1: something wrong
 * called by:   it's the function of a group of threads.
 */
static ssize_t
mail_handler(WTI * wti,CONN_INFO *cip, void *data, void **res)
{
    DATA_BLOCK *dbn;
    int len = 0;
    int all = 0;
    int retval;
    LMTP_DATA *ldp;
    char buf[MAX_BUF_LEN+1];
    //int re;

    CSF_UNUSED_ARG(res);
    CSF_UNUSED_ARG(cip);
    CSF_UNUSED_ARG(wti);

    ldp = (LMTP_DATA *)data;
    /*while (1) {
           memset(buf, 0, MAX_BUF_LEN);
           re = mread_line(ldp->data_buffer, (char *)buf, MAX_BUF_LEN);
           if (0 == re) {
               break;
            }
         if (parseline(ldp->pm, buf, (int)strlen(buf)) == ERROR) {
               fprintf(stderr, "parse ERROR\n"); 
               return (PIPELINE_FAILED);
            }
       }*/
    
    dbn = TAILQ_FIRST(&(ldp->data_buffer->head));
    if (dbn == NULL) {
        return (PIPELINE_FAILED);
    }

    if (dbn->data == NULL)
        return (PIPELINE_CONTINUE);
    
    len = getnstr(buf, dbn->data+all, MAX_BUF_LEN);
    while (len) {
        all += len + 2;
        if (all > dbn->data_len)
            break;
        retval = mail_parser(&(ldp->mi), buf);
        str2ip(ldp->mi.ip_str, &ldp->mi.ip_addr);
        if (retval == 0)
            break;
        len = getnstr(buf, dbn->data+all, MAX_BUF_LEN);
    }
    
    return (PIPELINE_CONTINUE);
}

int 
mod_init(char *mod_name)
{
    CSF_UNUSED_ARG(mod_name);

    set_request_init(NULL);
    set_request_deinit(NULL);
    set_request_handler(mail_handler);

    return (0);
}

#ifdef __cplusplus
};
#endif

